using System;
using System.Collections.Generic;
using System.Reflection;
using LispLib;
using Microsoft.DirectX;

namespace Noein.Control
{
    /// <summary>
    /// Taking a line of Lisp code (math function with dt as input), evaluate and set the
    /// given attribute to the result.
    /// HACK: very much hard-coded for now, only support 4 variables: x, y, z, and dt
    /// </summary>
    public class ScriptController : Controller
    {
        private Interpreter interpreter = new Interpreter();
        private Interpreter.Pair parsedTree;
        private string property;

        private delegate decimal D1(decimal f);

        public ScriptController(Interpreter.Pair parsedTree, string property)
        {
            this.parsedTree = parsedTree;
            this.property = property;

            Setup();
        }

        public static decimal Sin(decimal f)
        {
            return Convert.ToDecimal(System.Math.Sin(decimal.ToDouble(f)));
            //return (float)(System.Math.Round(System.Math.Sin(), 4));
        }
        
        public ScriptController(string script, string property)
        {
            // parse the script into a excutable tree
            int pos = 0;
            parsedTree = Interpreter.ReadOneExpr(script, ref pos, script.Length) as Interpreter.Pair;
            this.property = property;

            Setup();
        }

        private void Setup()
        {
            // add custom functions to interpreter
            D1 d1 = (D1)Delegate.CreateDelegate(
                typeof(D1), null,
                typeof(ScriptController).GetMethod("Sin", BindingFlags.Public | BindingFlags.Static));
            Interpreter.BoundFunction bf = new Interpreter.BoundFunction(interpreter.Globals, d1);
            interpreter.Globals.Set(@"sin", bf);
        }

        private double t = 0;

        public override void Update(Object obj, float dt)
        {
            if (Pause)
                return;

            t += dt;
            //t %= (float)System.Math.PI * 2;

            // get reference to variable
            string[] split = property.Split(new char[]{ '.', '[', ']' });
            object o = obj;
            int i = 0, j = split.Length;
            int index;
            j -= (int.TryParse(split[split.Length - 1], out index)) ? 2 : 1;
            for (i = 0; i < j; i++)
            {
                if (split[i] == "")
                    continue;

                if (o.GetType().Name.Contains("List"))
                {
                    o = o.GetType().GetProperty("Item").GetValue(o, new object[] { int.Parse(split[i]) });
                }
                else
                {
                    o = o.GetType().GetProperty(split[i]).GetValue(o, null);
                }
            }
            PropertyInfo p = o.GetType().GetProperty(split[i]);

            // update variables

            //float f = 0.000001f;
            //double d = 0.000001;
            //decimal dec = Convert.ToDecimal(f);

            //string s = f.ToString();
            //s = d.ToString();
            //s = dec.ToString();

            interpreter.Eval("(set (dt " + Convert.ToDecimal(dt).ToString() + "))");
            interpreter.Eval("(set (t " + Convert.ToDecimal(t).ToString() + "))");
            if (p.CanRead)
            {
                interpreter.Eval("(set (x " + (float)p.GetValue(o, null) + "))");
            }
            else
            {
                interpreter.Eval("(set (x 0))");
            }
            
            // run the script
            interpreter.Eval(parsedTree);

            // set variables in object
            if (i == split.Length - 1)
                p.SetValue(o, (float)(Convert.ToDouble(interpreter.Eval("x").car_)), null);
            else
                p.SetValue(o, (float)(Convert.ToDouble(interpreter.Eval("x").car_)), new object[] { index });
        }

        public override void AttachTo(Object obj)
        {
            if (obj.Spatial == null)
                obj.Spatial = new Spatial();

            string[] split = property.Split('.');
            object o = obj;
            int i = 0;
            for (i = 0; i < split.Length - 1; i++)
            {
                o = o.GetType().GetProperty(split[i]).GetValue(o, null);
            }
            PropertyInfo p = o.GetType().GetProperty(split[i]);
            if (p == null)
                throw new Exception("Object do not have the variable the script controller modifies");

            obj.Controllers.Add(this);
        }
    }
}
